#!/usr/bin/perl

##NWBrain 0.1.0-Dev

use strict;
use Getopt::Long qw(:config no_ignore_case bundling);
use Nmap::Parser;
use IO::File;
use Switch;
use DBI;

my ($DBNAME, $DBHOST, $DBTYPE, $DBUSER, $DBPASS);
my ($nmap_file, $nmap_dir, $config_file, $file, $IP);
my $mode = 'file';
my $nmap_path = '/usr/bin/nmap';
my $range = '1-1024';
my $nmap_args = '-PN -sV -p $range';
my @ips=();
my @files=();
my %opts=();
my %QUERY=();
my %config_info=();
$QUERY{DBTEST}
		= qq{CREATE DATABASE IF NOT EXISTS nwbrain};
$QUERY{TBLTEST1}
		= qq{CREATE TABLE IF NOT EXISTS `nwbrain`.`IP_OS` }
		. qq{(`IP` VARBINARY(256) NULL ,}
		. qq{`OS` VARBINARY(256) NULL ,}
		. qq{`timestamp` TIMESTAMP NULL ,}
		. qq{PRIMARY KEY (`IP`) )};
$QUERY{TBLTEST2}
		= qq{CREATE  TABLE IF NOT EXISTS `nwbrain`.`host_detail` }
		. qq{(`IP_OS_FK` VARBINARY(256) NULL ,}
		. qq{`port` VARBINARY(256) NULL ,}
		. qq{`protocol` VARBINARY(256) NULL ,}
		. qq{`application` VARBINARY(256) NULL ,}
		. qq{PRIMARY KEY (`port`, `IP_OS_FK`, `application`),}
		. qq{FOREIGN KEY (`IP_OS_FK`) REFERENCES `nwbrain`.`IP_OS` (`IP`) )}; 
$QUERY{INSERT1} #Set insert query for host table in nwbrain
        = qq{REPLACE INTO}
        . qq{ IP_OS }
        . qq{(IP, OS, timestamp) VALUES (?,?,now())};
$QUERY{INSERT2} #Set insert query for port/service table in nwbrain
        = qq{REPLACE INTO}
        . qq{ host_detail }
        . qq{(IP_OS_FK, port, protocol, application) VALUES (?,?,?,?)};

sub process {
        my $host = shift; #pop host off front of array
        my $name;
        my $host_ip = $host->addr();
        my $os = $host->os_sig();
        if (!$os->name()){
                if ($host->mac_vendor() =~ /Cisco/) {
                        $name = $host->mac_vendor();
                        $name = $name.' Device';
                } else {
                        $name = $os->vendor();
                }
        } else {
                $name = $os->name();
        }
        next unless $name =~ /^\w|\d/;
        my @Q1values = (
                        $host_ip,
                        $name     || undef
                        ##add timestamp value here
        );
        our $sth_ins1->execute(@Q1values);
                #Below, eval for open ports, if exists, delete host_detail table for host.
        my $sth3 = our $dbh->prepare_cached("DELETE FROM host_detail WHERE IP_OS_FK='$host_ip'");
#add timestamp criteria
        $sth3->execute();
        foreach ($host->tcp_open_ports) { #loop for tcp services

                        my $svc=$host->tcp_service($_);
                        my $tcp_temp=$svc->name;
                        my @Q2values = (
                                        $host_ip,
                                        $_,
                                        'TCP',
                                        $tcp_temp || undef
                        );
                        foreach (@Q2values) {print "$_\t";}
                        our $sth_ins2->execute(@Q2values);
        @Q2values=(); #flush array
        }
        foreach ($host->udp_open_ports) { #loop for udp services
                        my $svc=$host->udp_service($_);
                        my $udp_temp=$svc->name;
                        my @Q2values = (
                                        $host_ip,
                                        $_,
                                        'UDP',
                                        $udp_temp || undef
                        );
                        our $sth_ins2->execute(@Q2values);
                        my $ry = $sth_ins2->execute(@Q2values) ? "ok" : "OOPS! - " . DBI->errstr;
                        #printf( "\t..> %-15s : (%4s) : %-s\n", $host->addr, $host->status, $ry );;
                        @Q2values=(); #flush array
        }
}

sub cidr_deflator {
        my ($cidr) = @_;
        my @hosts = "";
        #http://www.perlmonks.org/?node_id=304414
        my $n = NetAddr::IP->new( $cidr );
        for my $ip( @{$n->hostenumref} ) {
                push(@hosts, $ip);
        }
        return @hosts;
}

sub trim {
    my ($feed) = @_;
    if ($feed) {
        $feed=~s/^\s*//;
        $feed=~s/\s*$//;
        return $feed;
    }
}

sub header {

print<<__EOT;
*********************************************
*       NWBRAIN - NMAP->MySQL UTILITY       *
*********************************************

__EOT
}

sub help {
print<<__EOT;
Usage: $0 -C <conf file>

  Options:
  -C location of conf file.
  -help/? Print this information

Valid DataBase types: mysql

__EOT
exit(0);
}

sub config_file_read {
    my ($conf_file, $conf_val) = @_;
    my ($line, $name, $value);

    open(CONFIG, "$conf_file") || die "Unable to open $conf_file!";
    while (<CONFIG>) {
        $line=$_;
        chomp($line);
        $line=trim($line);
        if (($line !~ /^#/) && ($line ne "")) {
            ($name, $value) = split(/=/, $line);
            $$conf_val{$name}=$value;
        }
    }
close(CONFIG);
}

GetOptions ("n=s" => \$nmap_file,
                "D=s"      => \$nmap_dir,
                "C=s"      => \$config_file,
                "I=s"      => \$IP,
                "mode=s"   => \$mode,
                "dbname=s" => \$DBNAME,
                "dbhost=s" => \$DBHOST,
                "dbtype=s" => \$DBTYPE,
                "dbuser=s" => \$DBUSER,
                "dbpass=s" => \$DBPASS);
##DO FILE VS SCAN LOGIC HERE
        ##CONDITIONAL 1)FILE; 2)SCAN
#ARG Sanity Checks
if ($config_file && -f $config_file) {
    &config_file_read($config_file, \%config_info);
}
if (!$DBNAME) {
    $DBNAME = $config_info{'dbname'};
} else {
    print "You need to specify a database file to output to\n\n";
    help();
}
if (!$DBHOST) {
    $DBHOST = $config_info{'dbhost'};
} else {
    print "You need to specify a database host to connect to!\n\n";
    help();
}
if (!$DBTYPE) {
    $DBTYPE = $config_info{'dbtype'};
} else {
        $DBTYPE = 'mysql';
#####Define valid types later, for now, mysql is only valid type
#    print "You need to specify a database type (mysql)!\n\n";
#    help();
}
if (!$DBUSER) {
    $DBUSER = $config_info{'dbuser'};
} else {
    print "You need to specify a username to connect to the database with!\n\n";
    help();
}
if (!$DBPASS) {
    $DBPASS = $config_info{'dbpass'};
} else {
    print "You need to specify a password to connect to the database with!!\n\n";
    help();
}
if (!$nmap_dir && $mode == 'file') {
    $nmap_dir = $config_info{'nmap_dir'};
    if ((!$nmap_dir) && (!$nmap_file)) {
        print "Specify an nmap file, or provide a directory of nmap files!\n\n";
        help();
    }
}
#Execution logic
my $np = new Nmap::Parser;
my $dbtest;
my $dsn = "DBI:$DBTYPE";
$dsn .= ":host=$DBHOST"; # if $DBTYPE eq 'mysql';
my $sth = '';
our $dbh = eval { DBI->connect( $dsn, $DBUSER, $DBPASS ) };
$dbtest = $dbh->prepare( $QUERY{DBTEST} );
	$dbtest->execute or die "Couldn't validate Database: " . $dbh->errstr;
$dbtest = $dbh->prepare( $QUERY{TBLTEST1} );
	$dbtest->execute or die "Couldn't validate IP_OS table: " . $dbh->errstr;
$dbtest = $dbh->prepare( $QUERY{TBLTEST2} );
	$dbtest->execute or die "Couldn't validate host_detail table: " . $dbh->errstr;
$dsn .= ":$DBNAME";
our $sth_ins1 = eval { $dbh->prepare( $QUERY{INSERT1} ) }
        or die "Couldn't prepare statement: " . $sth_ins1->errstr;
our $sth_ins2 = eval { $dbh->prepare( $QUERY{INSERT2} ) }
        or die "Couldn't prepare statement: " . $sth_ins2->errstr;
#if ($mode = 'file') {
        $np->callback( \&process); #used for cached processing of nmap xml files
        my $dsn = "DBI:$DBTYPE";
        $dsn .= ":host=$DBHOST"; # if $DBTYPE eq 'mysql';
        $dsn .= ":$DBNAME";
        my $sth = '';
        our $dbh = eval { DBI->connect( $dsn, $DBUSER, $DBPASS ) };
        our $sth_ins1 = eval { $dbh->prepare( $QUERY{INSERT1} ) }
                        or die "Couldn't prepare statement: " . $sth_ins1->errstr;
        our $sth_ins2 = eval { $dbh->prepare( $QUERY{INSERT2} ) }
                        or die "Couldn't prepare statement: " . $sth_ins2->errstr;
        if ($nmap_dir && -d $nmap_dir) {
                @files = <$nmap_dir/*>;
                foreach $nmap_file (@files) {
                        $np->parsefile($nmap_file);
                        print "\nProcessing file: $nmap_file...";
                }
        }
        if ($nmap_file && -f $nmap_file) {
                $np->parsefile($nmap_file); #parse nmap xml file
                        print "\nProcessing file: $nmap_file...";
        }
#} elsif ($mode = 'scan') {
#       if (!$IP) {
#               print "While using the scan mode, you must specify an IP or IP range to scan with the -I switch!\n";
#               help();
#       } else {
#               if ($IP =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
#                       @ips = split(/,/,$IP);
#               } elsif ($IP =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/\d{1,2}/) {
#                       @ips = cidr_deflator($IP);
#               } else { @ips = $IP; }
#print @ips;
#               my $dsn = "DBI:$DBTYPE";
#               $dsn .= ":host=$DBHOST"; # if $DBTYPE eq 'mysql';
#               $dsn .= ":$DBNAME";
#               my $sth = '';
#               our $dbh = eval { DBI->connect( $dsn, $DBUSER, $DBPASS ) };
#               our $sth_ins1 = eval { $dbh->prepare( $QUERY{INSERT1} ) }
#                               or die "Couldn't prepare statement: " . $sth_ins1->errstr;
#               our $sth_ins2 = eval { $dbh->prepare( $QUERY{INSERT2} ) }
#                               or die "Couldn't prepare statement: " . $sth_ins2->errstr;
#               $np->parsescan($nmap_path, $nmap_args, @ips);
#       }
#}
our $sth_ins1->finish;
our $sth_ins2->finish;
$dbh->disconnect;
print "\nAll Done!\n";
__END__

